using System.Net;
using System.Net.Sockets;
using Pinwheel.LSerialize;

namespace Pinwheel.Kernel
{
    public partial class Client : ISocketListener
    {
        private Connection m_Connection;
        private Rpc m_Rpc;
        
        public void OnConnected(ISocketSession session)
        {
            Log.Info($"[Client]Connect to {session.ReceiveEventArgs.RemoteEndPoint} success.");
            m_Connection = new Connection(null!, session);
        }

        public void OnConnectFailed(ISocketSession session)
        {
            Log.Info($"[Client]Connect to {session.ReceiveEventArgs.RemoteEndPoint} failed.");
        }
        
        public void OnReceive(ISocketSession session)
        {
            byte[] buffer = null!;
            var size = session.Received.Pop(ref buffer, 0);
            if (size > 0) Task.Factory.StartNew(() => OnReceiveAsync(buffer)); 
        }

        private async ValueTask OnReceiveAsync(byte[] buffer)
        {
            var packet = m_Connection.Decode(buffer);
            await OnReceiveAsync(packet);
        }

        private async ValueTask OnReceiveAsync(NetPacket packet)
        {
            var message = LSerializer.Deserialize<ServiceMessage>(packet.Data);
            switch (message.Type)
            {
                case MessageType.Response:
                    var tcs = m_Rpc.HandleReceive(ref message);
                    if (null == tcs) throw new Exception($"[Client]OnReceiveAsync get response but no request found: {message}");
                    break;
                case MessageType.Client:
                    Assert.Check(message.Session > 0, $"[Client]OnReceiveAsync request message session is zero.");
                    var response = await OnReceiveRequest(message);
                    message.SetType(MessageType.Response);
                    message.SetMessage(response);
                    
                    var data = LSerializer.Serialize(ref message);
                    packet.SetType((byte)MessageType.Response);
                    packet.SetData(data);
                    
                    await m_Connection.SendAsync(packet);
                    break;
                case MessageType.Service:
                default:
                    throw new ArgumentOutOfRangeException();
            } 
        }
        
        public void OnError(ISocketSession session, SocketError error)
        {
            OnClose(session);
        }

        public void OnClose(ISocketSession session)
        {
            var endPoint = session.ReceiveEventArgs.RemoteEndPoint as IPEndPoint;
            Log.Info($"[Client]Close connection {endPoint}.");
            m_Connection = null;
        }
    }
}